package org.epanetgrid.otimizacao.ag;

import java.util.LinkedList;
import java.util.List;

import org.epanetgrid.otimizacao.util.OtimizacaoUtil;
import org.jheuristics.Individual;
import org.jheuristics.ga.GAConfig;
import org.jheuristics.ga.GAStatus;
import org.jheuristics.ga.operators.Probability;
import org.jheuristics.ga.operators.Reproductor;
import org.jheuristics.ga.operators.reproductors.AbstractMutation;
import org.jheuristics.util.BitString;


/**
 * Efetua muta��o baseada no ponto de erro do cromossomo (ver especifica��es para maior detalhe)
 * @author Marcell Manfrin, marcell@ourgrid.org, Sep 30, 2005
 */
public class MutacaoDirecionada extends AbstractMutation {

	/**
	 * @see org.jheuristics.ga.operators.reproductors.AbstractMutation#AbstractMutation(Probability, int)
	 */
	public MutacaoDirecionada(Probability probability, int tryOuts) {
		super(probability, tryOuts);
	}

	/**
	 * @see org.jheuristics.ga.operators.reproductors.AbstractMutation#AbstractMutation(Reproductor, Probability, int)
	 */
	public MutacaoDirecionada(Reproductor decoretedReproductor, Probability probability, int tryOuts) {
		super(decoretedReproductor, probability, tryOuts);
	}


	/* (non-Javadoc)
	 * @see org.jheuristics.ga.operators.Reproductor#getRequiredNumberOfParents()
	 */
	public int getRequiredNumberOfParents() {
		if (null == getDecoratedReproductor()) {
			return super.getRequiredNumberOfParents();
		}
		return getDecoratedReproductor().getRequiredNumberOfParents();
	}

	/* (non-Javadoc)
	 * @see org.jheuristics.ga.operators.Reproductor#reproduce(org.jheuristics.Individual[], org.jheuristics.ga.GAStatus, org.jheuristics.ga.GAConfig)
	 */
	public Individual[] reproduce(Individual[] parents, GAStatus status, GAConfig config) {
		if (null == parents) {
			throw new NullPointerException();
		}
		if (getRequiredNumberOfParents() > parents.length && getRequiredNumberOfParents() != 0) {
			throw new IllegalArgumentException();
		}
		Individual[] individuals = super.reproduce(parents, status, config);
		if (null == getDecoratedReproductor()) {
			return individuals;
		}
		return decorate(individuals, status, config);
	}

	/**
	 *	Efetua muta��o no indiv�duo
	 * @param individual Indiv�duo que sofrer� muta��o
	 * @param status Status do AG
	 * @param config Configura��o atual do AG
	 * @return
	 * @see org.jheuristics.ga.operators.reproductors.AbstractMutation#mutate(org.jheuristics.Individual, org.jheuristics.ga.GAStatus, org.jheuristics.ga.GAConfig)
	 */
	@SuppressWarnings("unchecked")
	protected Individual mutate(Individual individual, GAStatus status, GAConfig config) {
		SimpleFitness fitness = (SimpleFitness) individual.getFitness();
		if (1 > fitness.getIntervaloDeErro()) {
			return null;
		}
		
		Object mutaded = null;
		int tipo = fitness.getTipoDeMutacao();
		boolean tipo1 = (SimpleFitness.MUTACAO_TIPO_1 & tipo) > 0;
		boolean tipo2 = (SimpleFitness.MUTACAO_TIPO_2 & tipo) > 0;
		boolean tipo3 = (SimpleFitness.MUTACAO_TIPO_3 & tipo) > 0;
		boolean tipo4 = (SimpleFitness.MUTACAO_TIPO_4 & tipo) > 0;
		List<BitString> gens = (List<BitString>) individual.getGens();
		if (!tipo3 && !tipo4) {
			if (tipo1 && tipo2) {
				// mutar 0 e 1
				mutaded = mutate(gens, fitness.getIntervaloDeErro()-1, getProbability(), status, config);
			} else {
				// mutar 1 se tipo == 1 ou mutar 0 se tipo == 2
				mutaded = mutate(gens, fitness.getIntervaloDeErro()-1, tipo1, getProbability(), status, config);
			}
		} else {
			if (tipo3 && tipo4) {
				// mutar 0 e 1
				mutaded = mutate(gens, Math.max(0, fitness.getIntervaloDeErro() - 3), getProbability(), status, config);
			} else {
				// mutar 1 se tipo == 3 ou mutar 0 se tipo == 4
				mutaded = mutate(gens, Math.max(0, fitness.getIntervaloDeErro() - 3), tipo3, getProbability(), status, config);
			}
		}
		if (null == mutaded || mutaded.equals(gens)) {
			return individual;
		}
		
		return config.getIndividualsFactory().createSpecificIndividual(mutaded, config);
	}

	/**
	 *
	 *	Efetua muta��o do indiv�duos
	 *
	 * @param gens genes do indiv�duo
	 * @param posicao posi��o que ir� sofrer muta��o
	 * @param value 
	 * @param probability Probabilidade de muta��o
	 * @param status Status do AG
	 * @param config Configura��o atual do AG
	 * @return Retorna o indiv�duo mutado
	 */
	private Object mutate(List<BitString> gens, int posicao, boolean value, Probability probability, GAStatus status, GAConfig config) {
		StringBuffer buffer = new StringBuffer();
		BitString string = new BitString(gens.get(posicao));
		boolean mutated = false;
		
		buffer.append("Cromossomo enviado para mutação direcionada (bit a bit): ");
		
		buffer.append(OtimizacaoUtil.gensToString(gens));
		buffer.append("\n");
		
		buffer.append("Verificando probabilidade de mutação direcionada(bit a bit):");
    	buffer.append("\n");
		
		for (int i = 0; i < string.length(); i++) {
			if (string.get(i) == value && probability.check(status, config)) {
				string.flip(i);
				mutated = true;
				buffer.append("Mutação efetuada intervalo: ");
				buffer.append(posicao+1);
				buffer.append(" no bit: ");
				buffer.append(i+1);
			}
		}
		if (mutated) {
			List<BitString> newGens = new LinkedList<BitString>(gens);
			newGens.set(posicao, string);
			buffer.append("\nCromossomo mutado: ");
			buffer.append(OtimizacaoUtil.gensToString(newGens));
			buffer.append("\n");
    		logger.debug(buffer);
			return newGens;
		}else {
			buffer.append("Cromossomo Não sofreu mutação\n\n");
			logger.debug(buffer);
		}
		return null;
	}

	/**
	 * 
	 * @param gens genes do indiv�duo
	 * @param posicao posi��o que ir� sofrer muta��o
	 * @param probability Probabilidade de muta��o
	 * @param status Status do AG
	 * @param config Configura��o atual do AG
	 * @return
	 */
	private Object mutate(List<BitString> gens, int posicao, Probability probability, GAStatus status, GAConfig config) {
		StringBuffer buffer = new StringBuffer();
		BitString string = new BitString((BitString) gens.get(posicao));
		boolean mutated = false;
		buffer.append("Cromossomo enviado para mutação direcionada (bit a bit): ");
		
		buffer.append(OtimizacaoUtil.gensToString(gens));
		buffer.append("\n");
		
		buffer.append("Verificando probabilidade de mutação direcionada(bit a bit):");
    	buffer.append("\n");
		for (int i = 0; i < string.length(); i++) {
			if (probability.check(status, config)) {
				string.flip(i);
				mutated = true;
				
				buffer.append("Mutação efetuada intervalo: ");
				buffer.append(posicao+1);
				buffer.append(" no bit: ");
				buffer.append(i+1);
				
			}
		}
		if (mutated) {
			List<BitString> newGens = new LinkedList<BitString>(gens);
			newGens.set(posicao, string);
			buffer.append("\nCromossomo mutado: ");
			buffer.append(OtimizacaoUtil.gensToString(newGens));
			buffer.append("\n");
    		logger.debug(buffer);
			return newGens;
		}else {
			buffer.append("Cromossomo Não sofreu mutação\n\n");
			logger.debug(buffer);
		}
		return null;
	}

}
